package com.panopset.net.ftp;

import static com.panopset.Util.*;
import static com.panopset.UtilIO.*;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * FTP Operation.
 *
 * @author Karl Dinwiddie
 *
 */
public final class Operation {

    /**
     * Default maximum number of retries is 10.
     */
    private static final int DEFAULT_MAX_RETRIES = 10;

    /**
     * Operation constructor.
     *
     * @param ftpServer
     *            FTP server to perform operation on.
     * @throws IOException
     *             IO Exception.
     */
    public Operation(final Server ftpServer) throws IOException {
        server = ftpServer;
    }

    /**
     * Set the maximumNumber of re-try attempts.
     *
     * @param maximumNumberOfRetrys
     *            The default is 10.
     */
    public void setMaxRetries(final Integer maximumNumberOfRetrys) {
        maxRetries = maximumNumberOfRetrys;
    }

    /**
     * If a connection is lost, the program will try to re-connect. To avoid an
     * infinite re-try loop that accomplishes nothing, this is the maximum
     * number of retries. The maximumRetries public static variable can safely
     * be set at any time. The default initial value is 10.
     */
    private Integer maxRetries;

    /**
     * Get max retries.
     *
     * @return Maximum number of retries.
     */
    private Integer getMaxRetries() {
        if (maxRetries == null) {
            maxRetries = DEFAULT_MAX_RETRIES;
        }
        return maxRetries;
    }

    /**
     * Skip existing files flag.
     */
    private boolean skipExisting = false;

    /**
     * Set to true if you want to skip existing files.
     *
     * @param value
     *            Default is false.
     */
    public void setSkipExisting(final boolean value) {
        skipExisting = value;
    }

    /**
     * @return true if skipExisting files flag is set.
     */
    private boolean isSkipExisting() {
        return skipExisting;
    }

    /**
     * Local path.
     */
    private String localPath;

    /**
     * Server.
     */
    private final Server server;

    /**
     * Execute FTP get file operation.
     *
     * @throws IOException
     *             IO Exception.
     */
    public void getFile() throws IOException {
        final File clientFile = new File(localPath);
        server.getFile(clientFile);
        close();
    }

    /**
     * Execute FTP put file operation.
     *
     * @throws IOException
     *             IO Exception
     */
    public void putFile() throws IOException {

        final File clientFile = new File(localPath);

        int retries = 0;
        while (retries++ < getMaxRetries()) {
            try {
                if (clientFile.isDirectory()) {
                    for (File f : clientFile.listFiles()) {
                        put(f);
                    }
                } else {
                    put(clientFile);
                }
                retries = Integer.MAX_VALUE;
            } catch (IOException ex) {
                error(ex);
                info("Trying again, retry #" + retries + " of "
                        + getMaxRetries());
                server.close();
                server.connect();
            }
        }
        close();
    }

    /**
     * Get response from command sent to server.
     *
     * @param command
     *            Command to send to server.
     * @return Response to command sent.
     * @throws IOException
     *             IO Exception.
     */
    String getResponse(final String command) throws IOException {
        return server.sendCommand(command);
    }

    /**
     * Put file.
     *
     * @param file
     *            File to upload to FTP server.
     * @throws IOException
     *             IO Exception.
     */
    private void put(final File file) throws IOException {
        if (file.isDirectory()) {
            server.cwd(file.getName());
            for (File f : file.listFiles()) {
                put(f);
            }
            server.cwd("..");
        } else {
            if (isSkipExisting()) {
                String response = getResponse("size " + file.getName());
                if (response != null && response.indexOf("500") == -1) {
                    info("File exists, skipping: " + file.getName());
                }
            }
            info("transferring " + file.getCanonicalPath());
            stor(file);
        }
    }

    /**
     * Send the QUIT command to the FTP server.
     *
     * @throws IOException
     *             IO Exception.
     */
    public void disconnect() throws IOException {
        try {
            sendCommand("QUIT");
        } finally {
            server.close();
        }
    }

    /**
     * Send the PWD command to the server.
     *
     * @return Response from server.
     * @throws IOException
     *             IO Exception.
     */
    public String pwd() throws IOException {
        String response = sendCommand("PWD");
        String dir = null;
        if (response.startsWith("257 ")) {
            int firstQuote = response.indexOf('\"');
            int secondQuote = response.indexOf('\"', firstQuote + 1);
            if (secondQuote > 0) {
                dir = response.substring(firstQuote + 1, secondQuote);
            }
        }
        return dir;
    }

    /**
     * Store file.
     *
     * @param file
     *            File to upload to the server.
     * @return true if successful.
     * @throws IOException
     *             IO Exception.
     */
    public synchronized boolean stor(final File file) throws IOException {
        if (file.isDirectory()) {
            throw new IOException("FtpOp cannot upload a directory.");
        }

        String filename = file.getName();

        return server.stor(new BufferedInputStream(new FileInputStream(file)),
                filename);
    }

    /**
     * Enter ASCII mode for sending text files. This is usually the default
     * mode. Make sure you use binary mode if you are sending images or other
     * binary data, as ASCII mode is likely to corrupt them.
     *
     * @return true if successful &quot;200&quot; return code is received.
     * @throws IOException
     *             IO Exception.
     */
    public synchronized boolean ascii() throws IOException {
        return sendCommand("TYPE A").startsWith("200 ");
    }

    /**
     * Send command to FTP Server.
     *
     * @param command
     *            FTP command to send.
     * @return Response from server.
     * @throws IOException
     *             IOException.
     */
    public String sendCommand(final String command) throws IOException {
        return server.sendCommand(command);
    }

    /**
     * File list start position is 39.
     */
    private static final int FILE_LIST_START_POS = 39;

    /**
     * Retrieve all files in a directory.
     *
     * @param name
     *            Directory name.
     * @return true if successful.
     * @throws IOException
     *             IO Exception.
     */
    public boolean retrDir(final String name) throws IOException {
        File toFile = new File(localPath + "/" + name);
        mkdirs(toFile);

        server.cwd(name);

        for (String s : server.list()) {
            info(s);
            if (s.length() > FILE_LIST_START_POS) {
                String fn = s.substring(FILE_LIST_START_POS);
                info("Getting file: " + fn);
                server.getFile(new File(toFile.getCanonicalPath() + "/" + fn));
            } else {
                info("Ignoring line as too short: " + s);
            }
        }
        return false;
    }

    /**
     * Close the FTP server connection.
     *
     * @throws IOException
     *             IO Exception.
     */
    public synchronized void close() throws IOException {
        server.close();
    }

    /**
     * Set local path.
     *
     * @param localSystemPath
     *            Local system directory where files are sent or retrieved.
     */
    public void setLocalPath(final String localSystemPath) {
        localPath = localSystemPath;
    }

    /**
     * Send info message to user.
     *
     * @param message
     *            Message to send.
     */
    protected void info(final String message) {
        dspmsg(message);
    }

    /**
     * Log any exceptions.
     *
     * @param t
     *            Error to log.
     */
    protected void error(final Throwable t) {
        log(t);
    }

    /**
     * Operation builder.
     */
    public static final class Builder {

        /**
         * Site name.
         */
        private String siteName;

        /**
         * Remote path.
         */
        private String remotePath;

        /**
         * Local path.
         */
        private String localPath;

        /**
         * User.
         */
        private String user;

        /**
         * Password.
         */
        private String password;

        /**
         * Max retries flag.
         */
        private Integer maxRetries;

        /**
         * Skip existing files flag.
         */
        private boolean skipExisting = false;

        /**
         * Operation constructor.
         *
         * @return Operation object.
         * @throws IOException
         *             IO Exception.
         */
        public Operation construct() throws IOException {
            Operation rtn = new Operation(new Server(siteName, remotePath,
                    user, password));

            if (localPath != null) {
                rtn.setLocalPath(localPath);
            }

            if (maxRetries != null) {
                rtn.setMaxRetries(maxRetries);
            }
            rtn.setSkipExisting(skipExisting);
            return rtn;
        }

        /**
         * Set FTP server name.
         * @param ftpServerName FTP server name.
         * @return Builder.
         */
        public Builder siteName(final String ftpServerName) {
            siteName = ftpServerName;
            return this;
        }

        /**
         * Set FTP server path.
         * @param ftpServerPath FTP server path.
         * @return Builder.
         */
        public Builder remotePath(final String ftpServerPath) {
            remotePath = ftpServerPath;
            return this;
        }

        /**
         * Set local system path.
         * @param localSystemPath Local system path.
         * @return Builder.
         */
        public Builder localPath(final String localSystemPath) {
            localPath = localSystemPath;
            return this;
        }

        /**
         *
         * @param serverUser Server user.
         * @return Builder.
         */
        public Builder user(final String serverUser) {
            user = serverUser;
            return this;
        }

        /**
         * Set server password.
         * @param serverPassword Server password.
         * @return Builder.
         */
        public Builder password(final String serverPassword) {
            password = serverPassword;
            return this;
        }

        /**
         * Set the maximum number of times to re-try connecting.
         *
         * @param maximumRetries
         *            Default is 10.
         * @return Builder.
         */
        public Builder maxRetries(final int maximumRetries) {
            maxRetries = maximumRetries;
            return this;
        }

        /**
         * Default behavior is to overwrite existing files. Call this method to
         * cause existing files to be skipped.
         *
         * @return Builder.
         */
        public Builder skipExisting() {
            this.skipExisting = true;
            return this;
        }
    }
}
